<?php
/**
 * @Author: feidao@ibantang.com, 2019, all rights reserved
 * @Date: 2019-11-15
 * @Time: 10:32
 * @Brief:
 */

namespace Core\Common\Base;

/**
 * 配置数据源的获取
 * 1) 从ini文件获取
 * 2) 从consul获取
 * Class ConfigSource
 * @package Core\Common\Base
 */
class ConfigSource
{
    private static $appConfigKey = "app_config";
    public static $serviceGatewayRouteKVName = "config/ares_gateway_route";

    /**
     * @var \Swoole\Table
     */
    private static $appConfigTable;

    /**
     * 从consul获取初始配置文件
     * @param $table
     */
    public static function initTableAndInitConfig()
    {
        $table = new \Swoole\Table(2);
        $table->column('data', \Swoole\Table::TYPE_STRING, 409600); // 400K
        $table->column('last_time', \Swoole\Table::TYPE_INT, 4);
        $table->column('md5', \Swoole\Table::TYPE_STRING, 32);
        $table->create();
        self::$appConfigTable = $table;

        // 框架主进程启动后，先从consul获取配置
        $value = self::getApplicationConfigFromConsul(null, 3);
        return [
            "table" => $table,
            "ok" => "OK" == $value["status_code"],
            "msg" => $value["msg"],
        ];
    }

    /**
     * 刷新swoole table里的配置信息
     * @param $value
     * @return bool
     */
    public static function resetAppConfig($value)
    {
        Log::coreLog([
            "debug consul resetAppConfig",
            time("Y-m-d H:i:s"),
            strlen($value),
        ]);
        if (empty($value)) {
            // 如果数据为空，则不记录
            return;
        }
        if (empty(self::$appConfigTable)) {
            return;
        }

        $newMd5 = md5($value);

        // 先判断是否和已有的值一样
        $oldValue = self::$appConfigTable->get(self::$appConfigKey);
        if (!empty($oldValue) && isset($oldValue["md5"]) && $newMd5 == $oldValue["md5"]) {
            Log::coreLog([
                "debug consul, same md5 value, ignore",
            ]);
            return;
        }
        return self::$appConfigTable->set(self::$appConfigKey, [
            "data" => $value,
            "last_time" => microtime(true),
            "md5" => $newMd5,
        ]);
    }

    public static function getResetAppConfigTime()
    {
        if (empty(self::$appConfigTable)) {
            return;
        }
        $value = self::$appConfigTable->get(self::$appConfigKey);
        if (empty($value)) {
            return null;
        }
        return $value["last_time"] ?? null;
    }

    /**
     * 获取应用配置文件内容
     */
    public static function getAppConfig()
    {
        // 兼容crontab，在没有初始化的情况下调用，直接返回NULL
        $cnt = self::isUseConsul() ? self::loadConsulConfig() : self::loadLocalConfig();
        return parse_ini_string($cnt);
    }

    /**
     * 配置文件是否发生了变化
     * @return bool
     */
    public static function needReload($preData, $preTime): bool
    {
        if (self::isUseConsul()) {
            if (is_null($preData)) {
                return true;
            }

            // 检查swoole table里的数据是否有更新
            $value = self::$appConfigTable->get(self::$appConfigKey);
            if (empty($value)) {
                // 如果当前swoole table里没有数据，则不更新
                return false;
            }

            if (empty($value["data"])) {
                return true;
            }

            // swoole里的时间比当前进程的时间更新，说明需要更新了
            $oldTime = $value["last_time"];
            if ($oldTime > $preTime) {
                return true;
            }
        }
        // 使用 local file ，不存在发生变化
        if (is_null($preData)) {
            return true;
        }
        return false;
    }

    /**
     * 从consul加载配置文件
     */
    private static function loadConsulConfig()
    {
        if (empty(self::$appConfigTable)) {
            return;
        }
        $value = self::$appConfigTable->get(self::$appConfigKey);
        if (empty($value)) {
            return null;
        }
        return $value["data"];
    }

    /**
     * 从本地加载配置文件
     * @return false|string|void
     */
    private static function loadLocalConfig()
    {
        if (file_exists(CORE_CONFIG_PATH . 'app-' . CORE_ENV . '.ini')) {
            $filepath = CORE_CONFIG_PATH . 'app-' . CORE_ENV . '.ini';
        } elseif (file_exists(CORE_CONFIG_PATH . 'app.ini')) {
            $filepath = CORE_CONFIG_PATH . 'app.ini';
        } else {
            return "";
        }
        return file_get_contents($filepath);
    }

    /**
     * 是否需要从consul加载配置
     * @return bool
     */
    public static function isUseConsul()
    {
        return defined('CORE_USE_CONSUL_CONFIG') && CORE_USE_CONSUL_CONFIG;
    }

    /**
     * @return |null
     */
    public static function getApplicationConfigFromConsul($options = null, $maxTries = 1)
    {
        $kvInstance = Consul::getInstance()->KV();
        $configPath = 'config/' . CORE_NAME . '/data';
        $kvPair = null;
        $time = time("Y-m-d H:i:s");
        $result = [
            "status_code" => "OK",
            "msg" => "",
            "time" => $time,
        ];
        $err3 = null;
        while ($maxTries-- > 0) {
            try {
                list($kvPair, $_, $error) = $kvInstance->get($configPath);
                if (!empty($error)) {
                    $err3 = $error;
                    continue;
                }
                if ($kvPair && !empty($kvPair->Value)) {
                    break;
                }
            } catch (\Throwable $err2) {
                $err3 = $err2->getMessage();
            }
        }
        if (empty($kvPair) || empty($kvPair->Value)) {
            $result["status_code"] = "error3";
            $result["msg"] = "consul {$configPath} is empty, {$err3}";
            return $result;
        }
        self::resetAppConfig($kvPair->Value);
        return $result;
    }

    public static function getGatewayRouteConfig($maxTries = 3)
    {
        $kvInstance = Consul::getInstance()->KV();
        $configPath = self::$serviceGatewayRouteKVName;
        $kvPair = null;
        $err3 = null;
        while ($maxTries-- > 0) {
            try {
                list($kvPair, $_, $error) = $kvInstance->get($configPath);
                if (!empty($error)) {
                    $err3 = $error;
                    continue;
                }
                if ($kvPair && !empty($kvPair->Value)) {
                    break;
                }
            } catch (\Throwable $err2) {
                $err3 = $err2->getMessage();
            }
        }
        if (empty($kvPair) || empty($kvPair->Value)) {
            $error = [
                __FUNCTION__,
                __FILE__,
                "consul {$configPath} is empty, {$err3}",
                "consul addr is " . CORE_CONSUL_AGENT,
            ];
            return ["value" => null, "error" => $error,];
        }
        return ["value" => $kvPair->Value, "error" => "",];
    }
}
